package cn.always.xiajia.framework.security.config;

import javax.annotation.Resource;

import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

import cn.always.xiajia.admin.client.api.oauth2.OAuth2AccessTokenApi;
import cn.always.xiajia.admin.client.api.sys.SysMenuApi;
import cn.always.xiajia.framework.common.config.RpcProperties;
import cn.always.xiajia.framework.common.config.SecurityProperties;
import cn.always.xiajia.framework.security.core.aop.PreAuthenticatedAspect;
import cn.always.xiajia.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
import cn.always.xiajia.framework.security.core.filter.TokenAuthenticationFilter;
import cn.always.xiajia.framework.security.core.handler.AccessDeniedHandlerImpl;
import cn.always.xiajia.framework.security.core.handler.AuthenticationEntryPointImpl;
import cn.always.xiajia.framework.security.core.service.SecurityFrameworkService;
import cn.always.xiajia.framework.security.core.service.impl.SecurityFrameworkServiceImpl;
import cn.always.xiajia.framework.web.core.handler.GlobalExceptionHandler;

@AutoConfiguration
@EnableConfigurationProperties({ SecurityProperties.class, RpcProperties.class })
public class XiaJiaSecurityAutoConfiguration {

	@Resource
	private SecurityProperties securityProperties;

	@Resource
	private RpcProperties rpcProperties;

	/**
	 * 处理用户未登录拦截的切面的 Bean
	 */
	@Bean
	public PreAuthenticatedAspect preAuthenticatedAspect() {
		return new PreAuthenticatedAspect();
	}

	/**
	 * 认证失败处理类 Bean
	 */
	@Bean
	public AuthenticationEntryPoint authenticationEntryPoint() {
		return new AuthenticationEntryPointImpl();
	}

	/**
	 * 权限不够处理器 Bean
	 */
	@Bean
	public AccessDeniedHandler accessDeniedHandler() {
		return new AccessDeniedHandlerImpl();
	}

	/**
	 * Spring Security 加密器 考虑到安全性，这里采用 BCryptPasswordEncoder 加密器
	 *
	 * @see <a href=
	 *      "http://stackabuse.com/password-encoding-with-spring-security/">Password
	 *      Encoding with Spring Security</a>
	 */
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder(securityProperties.getPasswordEncoderLength());
	}

	/**
	 * Token 认证过滤器 Bean
	 */
	@Bean
	public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler,
			OAuth2AccessTokenApi oAuth2AccessTokenApi) {
		return new TokenAuthenticationFilter(securityProperties, rpcProperties, globalExceptionHandler, oAuth2AccessTokenApi);
	}

	@Bean("ss") // 使用 Spring Security 的缩写，方便使用
	public SecurityFrameworkService securityFrameworkService(SysMenuApi sysMenuApi) {
		return new SecurityFrameworkServiceImpl(sysMenuApi);
	}

	/**
	 * 声明调用 {@link SecurityContextHolder#setStrategyName(String)} 方法， 设置使用
	 * {@link TransmittableThreadLocalSecurityContextHolderStrategy} 作为 Security
	 * 的上下文策略
	 */
	@Bean
	public MethodInvokingFactoryBean securityContextHolderMethodInvokingFactoryBean() {
		MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
		methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);
		methodInvokingFactoryBean.setTargetMethod("setStrategyName");
		methodInvokingFactoryBean.setArguments(TransmittableThreadLocalSecurityContextHolderStrategy.class.getName());
		return methodInvokingFactoryBean;
	}

}
